// A.K.A. Freshness
// related "TypeScript 小技巧 (2024_10_28 10_20_00).html"
import type { IsEqual, UnionToIntersection, Tagged, Spread } from 'type-fest';
import {} from 'ts-brand';
// https://flow.org/en/docs/lang/nominal-structural/#toc-nominal-typing
// typescript 的 class(的类型检验) (一般) 是鸭子类型 (结构类型), 与 名义类型 相对

// 结构类型：主类型如果对于第二个元素类型中的每个特征，第一个元素的类型中存在相应的相同特征，则认为一个元素与另一个元素兼容。
//  使用 duck 类型时，如果对象具有该类型所需的所有方法和属性，则该对象属于给定类型。
//  某些语言可能在细节上有所不同，例如特征是否必须在名称上匹配。

// 名义类型：主类型如果对象被声明为给定类型(或者如果类型与对象的关联是通过对象继承等机制推断的)，则该对象属于给定类型。代表每一个类型都是唯一的。
//  即相同字段的不同类型属于不同的类型。

// # 例子：函数签名
// 在 结构类型 中，函数签名需要完全相同 才可调用*(通过检查)
// 在 名义类型 中，函数签名可以更多 (更具体)，即可调用

//  限制访问成员属性 虽然在使用中被忽略，但是确实影响了这种计算 (效果像用了 hash 标注)
// 即 有限制访问成员属性 的类就成为了 名义类型
{
  // # Implicit Nominal Typing 隐式名义类型
  class Foo {}
  class Bar {}
  class Baz {
    protected __name!: string;
  }
  class Fok {
    private __name!: string;
  }
  class Bak {
    declare __name: string;
  }
  // 这里的 Foo 是 Bar 的子类
  type FB = IsEqual<Foo, Bar>;
  //    ^?
  type FB1 = IsEqual<Bak, Baz>;
  //    ^?
  let foo: Foo = new Bar();
  //> typescript class(在校验类型上) 是鸭子类型 (结构化类型 / 名义类型)
  //> *- declare 实际上是注册了对应的成员 (类型), 而 private 成员 (protected, static 同理) (似乎是) 注册了 hash;
  //> *-  所以相同 declare 的类 类型相同，相同 private(或) 的类 类型不同
}
{
  // # Shared Nominal Typing 共享名义类型
  // 这里尝试
  type FooLike = {
    _isFoo: FooLike;
  };

  // 这里可以 impl 或无，
  class Bar implements FooLike {
    declare _isFoo: this;
  }
  // 可以转，因为 declare 实际上注册了对应的成员
  let a: FooLike = new Bar();

  class UserDefined {}
  // @ts-expect-error - 不能转
  let b: FooLike = new UserDefined();
  b._isFoo;
}
{
  class Type<T> {
    of(value: T): Value<T> {
      return new Value(this, value);
    }
  }
  class Value<T> {
    /** @internal */
    constructor(
      readonly type: Type<T>,
      readonly value: T,
    ) {}

    static define<T>() {
      return new Type<T>();
    }

    is<T>(this: this, type: Type<any>): this is Value<T> {
      return this.type === type;
    }
  }

  // 定义一个类型叫温度
  const temperature = Value.define<number>();

  // 实例化这个类型
  const a = temperature.of(42);
  a.is(temperature); // true
}
// # extend
{
  class Animal {
    name: string = '';
  }
  class Dog extends Animal {
    override name: string = '';
  }

  var dog = new Dog();
  // Covariant 协变
  type AnimalFn = () => Animal;
  type DogFn = () => Dog;

  let dogFn!: DogFn;
  let animalFn: AnimalFn = dogFn;
  //> 这里体现了协变，因为 DogFn 类型的函数可以替换 AnimalFn 类型的函数

  // Contravariant 逆变
  type AnimalHandlerFn = (fn: AnimalFn) => void;
  type DogHandlerFn = (fn: DogFn) => void;
  let dogHandlerFn!: DogHandlerFn;
  let animalHandlerFn: AnimalHandlerFn = dogHandlerFn;
  //> 这里体现了逆变，因为 DogHandlerFn 类型的函数可以替换 HandlerFn 类型的函数

  Dog < Animal;
  dogFn < animalFn;

  animalHandlerFn < dogHandlerFn;
}

// # extend
{
  // Spread
  type Foo = {
    a: number;
    b?: string;
  };

  type Bar = {
    b?: number;
    c: boolean;
  };

  const foo = { a: 1, b: '2' };
  const bar = { c: false };
  const fooBar = { ...foo, ...bar };

  type FooBar = Spread<Foo, Bar>;
  // type FooBar = {
  // 	a: number;
  // 	b?: string | number | undefined;
  // 	c: boolean;
  // }

  const baz = (argument: FooBar) => {
    // Do something
  };

  baz(fooBar);
}
// ## 类型的赋值安全逻辑：只能是子类 (具体) 赋值给父类 (抽象)
// ## 即： [unknown(抽象) > never(具体)]
{
  // TypeScript is a Structural Type System. A structural type
  // system means that when comparing types, TypeScript only
  // takes into account the members on the type.

  // This is in contrast to nominal type systems, where you
  // could create two types but could not assign them to each
  // other. See example:nominal-typing

  // For example, these two interfaces are completely
  // transferrable in a structural type system:

  interface Ball {
    diameter: number;
  }
  interface Sphere {
    diameter: number;
  }

  let ball: Ball = { diameter: 10 };
  let sphere: Sphere = { diameter: 20 };

  sphere = ball;
  ball = sphere;

  // If we add in a type which structurally contains all of
  // the members of Ball and Sphere, then it also can be
  // set to be a ball or sphere.

  interface Tube {
    diameter: number;
    length: number;
  }

  let tube: Tube = { diameter: 12, length: 3 };
  // @ts-expect-error - 因为 Tube{ diameter: number; length: number; }(具体)< Ball{ diameter: number;}(抽象)
  tube = ball;
  ball = tube;

  // Because a ball does not have a length, then it cannot be
  // assigned to the tube variable. However, all of the members
  // of Ball are inside tube, and so it can be assigned.

  // TypeScript is comparing each member in the type against
  // each other to verify their equality.

  // A function is an object in JavaScript and it is compared
  // in a similar fashion. With one useful extra trick around
  // the params:

  let createBall = (diameter: number) => ({ diameter });
  let createSphere = (diameter: number, useInches: boolean) => {
    return { diameter: useInches ? diameter * 0.39 : diameter };
  };

  {
    // 参数值越少越抽象，但由于逆变，实际的结构要取反
    createSphere = createBall;
    // @ts-expect-error - (diameter: number)(抽象) <[逆变]< (diameter: number, useInches: boolean)(具体)
    createBall = createSphere;
  }

  // TypeScript will allow (number) to equal (number, boolean)
  // in the parameters, but not (number, boolean) -> (number)

  // TypeScript will discard the boolean in the first assignment
  // because it's very common for JavaScript code to skip passing
  // params when they're not needed.

  // For example the array's forEach's callback has three params,
  // value, index and the full array - if TypeScript didn't
  // support discarding parameters, then you would have to
  // include every option to make the functions match up:

  [createBall(1), createBall(2)].forEach((ball, _index, _balls) => {
    /* Deleted: */
    /* console.log(ball): Console C:\Users\szqp\AppData\Local\Programs\Microsoft VS Code */
    /* Deleted: */
    /* console["log"](""): Console */
  });

  // No one needs that.

  // Return types are treated like objects, and any differences
  // are compared with the same object equality rules above.

  let createRedBall = (diameter: number) => ({ diameter, color: 'red' });

  {
    // 返回值和平常值一样，成员更少更抽象
    createBall = createRedBall;
    // @ts-expect-error - 返回值中 { diameter: number; color: string; }(具体) < { (diameter: number) }(抽象)
    createRedBall = createBall;
  }

  // Where the first assignment works (they both have diameter)
  // but the second doesn't (the ball doesn't have a color).

  createSphere = createRedBall;
  // @ts-expect-error
  // createRedBall: (diameter: number) => { diameter: number; color: string; }
  // createSphere: (diameter: number, useInches: boolean) => { diameter: number; }
  createRedBall = createSphere;
}
